﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using XOS_VHD_Tools;
using XOS_VHD_Tools.VHD;
using System.ComponentModel.Composition;

namespace XOS_VHD_FAT32
{
    [Export(typeof(IVHDFSDriver))]
    sealed class FAT32Driver : VHDFSDriver
    {
        private VHDPartition _part;

        private DirEntry _root;

        internal long _clustOffs;
        internal int _sectPerClust;
        internal int _bytesPerSect;
        internal int _bytesPerClust;

        private int _fatOffs;
        private int _fatCount;

        internal uint[] _fatChain;

        internal uint GetNextClusterID(uint curClust)
        {
            if (curClust >= 0x0fffffff || curClust < 2)
            {
                return 0xffffffff;
            }

            return _fatChain[curClust];
        }

        internal long GetLBAForCluster(uint clustId)
        {
            if (clustId >= 0x0ffffff0 || clustId < 2)
            {
                return 0xffffffff;
            }

            return _clustOffs + (clustId - 2) * _sectPerClust;
        }

        internal byte[] ReadCluster(uint clustId)
        {
            Trace.WriteLine("ReadCluster(): clustId=0x" + clustId.ToString("X8"));

            if (clustId >= 0x0fffffff || clustId < 2)
            {
                return new byte[0];
            }

            var lba = GetLBAForCluster(clustId);

            return _part.ReadSectors(lba, _sectPerClust);
        }


        internal void WriteCluster(uint clustId, byte[] buff)
        {
            Trace.WriteLine("ReadCluster(): clustId=0x" + clustId.ToString("X8"));

            if (clustId < 0x0fffffff && clustId >= 2)
            {
                if (buff.Length != _bytesPerSect)
                {
                    throw new InvalidOperationException();
                }

                var lba = GetLBAForCluster(clustId);

                _part.WriteSectors(buff, lba, _sectPerClust);
            }
        }

        internal VHDPartition Partition { get { return _part; } }

        public override bool Initialize(VHDPartition part)
        {
            switch (part.Type)
            {
                case PartitionType.FAT32:
                case PartitionType.FAT32_LBA:
                case PartitionType.FAT32_HIDDEN:
                case PartitionType.FAT32_LBA_HIDDEN:
                case PartitionType.FAT32_SECURED:
                case PartitionType.FAT32_LBA_SECURED:
                    break;
                default:
                    return false;
            }

            _part = part;

            byte[] sectorData = _part.ReadSectors(0, 1);

            var bootSect = sectorData.ConvertTo<BootSect>();

            if (bootSect.Magic != 0xAA55 || bootSect.FileSystemType != "FAT32   " ||
                (bootSect.BootSignature != 0x28 && bootSect.BootSignature != 0x29))
            {
                //throw new ArgumentException("Specified partition is not a valid FAT32 partition", "part");
                return false;
            }

            _bytesPerSect = bootSect.BytesPerSector;

            var fat = new List<uint>(bootSect.FATSize32 * bootSect.BytesPerSector / 4);

            _fatOffs = bootSect.ReservedSectorsCount;
            var entries = _part.ReadSectors(_fatOffs, bootSect.FATSize32);
            for (int j = 0; j < entries.Length; j += 4)
            {
                fat.Add(BitConverter.ToUInt32(entries, j));
            }

            _fatChain = fat.ToArray();

            _fatCount = bootSect.FATCount;

            _clustOffs = bootSect.ReservedSectorsCount + bootSect.FATSize32 * _fatCount;

            _sectPerClust = bootSect.SectorsPerCluster;

            _bytesPerClust = _sectPerClust * _bytesPerSect;

            var rootDirClust = bootSect.RootCluster;

            _root = DirEntry.Create(bootSect, (uint)rootDirClust);

            return true;
        }

        public override IDirectory RootDirectory
        {
            get { return new Directory(_root, this, true); }
        }


        internal void FlushFAT()
        {
            var buff = _fatChain.Select(t => BitConverter.GetBytes(t)).SelectMany(t => t).ToArray();

            for (int fatC = 0; fatC < _fatCount; fatC++)
            {
                _part.WriteSectors(buff, _fatOffs + buff.Length * (fatC + 1), buff.Length / 512);
            }
        }

    }
}
